<?php
/**
 * Requirement repository
 */
namespace BWL\webshopBundle\Repository;
use BWL\webshopBundle\Entity\Item;
use BWL\webshopBundle\Entity\Ordering;
use BWL\webshopBundle\Entity\Requirement;
use BWL\webshopBundle\Entity\Assignment;
use BWL\webshopBundle\Entity\RequirementDerivation;
use BWL\webshopBundle\Entity\RequirementFulfilment;

use Doctrine\ORM\EntityRepository;

/**
 * RequirementRepository
 *
 * Requirement specific methods to locale entity.
 */
class RequirementRepository extends EntityRepository
{
  /**
   * Lead days (Vorlaufzeit)
   */
  const LEAD_DAYS = 1;

  /**
   * Find requirements in period
   * @param \DateTime $from
   * @param \DateTime $to
   * @return array
   */
  public function findByPeriod(\DateTime $from, \DateTime $to) {
    $to->modify('+1 day');
    $from->setTime(0,0);
    $qb = $this->createQueryBuilder('r')
      ->select('r');
    $qb->leftJoin('r.item', 'i');
    $qb->where('r.date >= ?1');
    $qb->andWhere('r.date < ?2');
    $qb->orderBy('i.name', 'ASC');
    $qb->addOrderBy('r.date', 'ASC');

    $qb->setParameters(array(
      1 => $from,
      2 => $to
    ));

    return $qb->getQuery()->getResult();
  }

  /**
   * Returns latest Requirement
   * @return mixed
   */
  public function findLatestRequirement() {
    return $this->findOneBy(array(), array('date' => 'DESC'));
  }

  /**
   * Return one Requirement by Component and Date (without time) or null.
   * @param \BWL\webshopBundle\Entity\Item $item
   * @param \DateTime $date
   * @return mixed
   */
  public function findByComponentAndDate(Item $item, \DateTime $date) {
    return $this->findOneBy(array('item' => $item, 'date' => $date));
  }

  /**
   * Returns primary requirements of a component in months
   * @param \BWL\webshopBundle\Entity\Item $item
   * @param \DateTime $from
   * @param \DateTime $to
   * @return mixed
   */
  public function findPrimaryRequirement(Item $item, \DateTime $from, \DateTime $to) {
    $qb = $this->createQueryBuilder('r')
      ->select('SUM(r.amount) AS amount');
    $qb->where('r.item = ?1');
    $qb->andWhere('r.date >= ?2');
    $qb->andWhere('r.date < ?3');
    $qb->groupBy('r.item');

    $qb->setParameters(array(
      1 => $item,
      2 => $from,
      3 => $to,
    ));

    return $qb->getQuery()->getOneOrNullResult();
  }

  /**
   * Updates all missing requirements.
   */
  public function updateByOrderings() {
    $em = $this->getEntityManager();
    $from = new \DateTime('2000-01-01');
    $to = new \DateTime();
    $to->setTime(0, 0);

    // Latest requirement
    $latest = $this->findLatestRequirement();
    if(false === is_null($latest)) {
      $from = $latest->getDate();
      $from->setTime(0, 0);
      $from->modify('+1 day');
    }

    // All orderings without calculated requirements
    $items = $em->getRepository('BWLwebshopBundle:Ordering')->findOrderingComponentsByPeriod($from, $to);
    foreach($items as $item) {
      $component = $em->getRepository('BWLwebshopBundle:Item')->find($item['item_id']);

      // Date of requirement is dispatch date (Bedarfsdatum = Auslieferungsdatum)
      $requirementDate = $item['dispatched']->setTime(0, 0);

      // Date of assignment (Produktionsauftragsdatum)
      // requirementdate minus productiontime
      $assignmentDate = clone $requirementDate;
      $assignmentDate->modify('-1 day');

      $requirement = $this->findByComponentAndDate($component, $requirementDate);
      if(is_null($requirement)) {
        $requirement = new Requirement();
        $requirement->setItem($component);
        $requirement->setDate($requirementDate);
        $requirement->setAmount($item['amount']);
      } else {
        $requirement->setAmount($requirement->getAmount() + $item['amount']);
      }
      $em->persist($requirement);
      $em->flush($requirement);

      $assignment = $em->getRepository('BWLwebshopBundle:Assignment')
        ->findByComponentAndDate($component, $assignmentDate, self::LEAD_DAYS);
      if(is_null($assignment)) {
        $assignment = new Assignment();
        $assignment->setItem($component);
        $assignment->setDate($assignmentDate);
        $assignment->setAmount($item['amount']);
      } else {
        $assignment->setAmount($assignment->getAmount() + $item['amount']);
      }
      $em->persist($assignment);
      $em->flush($assignment);

      $fulfilment = $em->getRepository('BWLwebshopBundle:RequirementFulfilment')->findByIds($assignment, $requirement);
      if(is_null($fulfilment)) {
        $fulfilment = new RequirementFulfilment();
        $fulfilment->setRequirement($requirement);
        $fulfilment->setAssignment($assignment);
        $em->persist($fulfilment);
        $em->flush($fulfilment);
        $em->clear($fulfilment);
      }
    }

    // All fulfilments
    $fulfilments = $em->getRepository('BWLwebshopBundle:RequirementFulfilment')->findByPeriod($from, $to);
    foreach($fulfilments as $fulfilment) {
      $requirement = $fulfilment->getRequirement();
      $component = $requirement->getItem();
      $amount = $requirement->getAmount();
      $requirementDate = $requirement->getDate();
      $assignmentDate = $fulfilment->getAssignment()->getDate();
      $this->updateRequirementsAndAssignmentsByComponent($component, $amount, $requirementDate, $assignmentDate);
    }
  }

  // --- Helper ---------------------------------------------------------------

  /**
   * Helper to update requirements and assignments of components with child-components recursive
   * @param \BWL\webshopBundle\Entity\Item $component
   * @param \DateTime $requirementDate
   */
  private function updateRequirementsAndAssignmentsByComponent(Item $component, $amount, \DateTime $requirementDate, \DateTime $assignmentDate) {
    $em = $this->getEntityManager();
    foreach($component->getWholeItems() as $structure) {
      $amount = $amount * $structure->getCount();
      $child_component = $structure->getPartItem();
      $childRequirementDate = clone $requirementDate;
      $childRequirementDate = $childRequirementDate->modify('-1 day');
      $childAssignmentDate = clone $assignmentDate;
      $childAssignmentDate = $childAssignmentDate->modify('-1 days');

      // Set requirements
      $requirement = $this->findByComponentAndDate($child_component, $childRequirementDate);
      if(is_null($requirement)) {
        $requirement = new Requirement();
        $requirement->setItem($child_component);
        $requirement->setDate($childRequirementDate);
        $requirement->setAmount($amount);
      } else {
        $requirement->setAmount($requirement->getAmount() + $amount);
      }
      $em->persist($requirement);
      $em->flush($requirement);
      $em->clear($requirement);

      // Set assignments
      $assignment = $em->getRepository('BWLwebshopBundle:Assignment')
        ->findByComponentAndDate($child_component, $childAssignmentDate, self::LEAD_DAYS);
      if(is_null($assignment)) {
        $assignment = new Assignment();
        $assignment->setItem($child_component);
        $assignment->setDate($childAssignmentDate);
        $assignment->setAmount($amount);
      } else {
        $assignment->setAmount($assignment->getAmount() + $amount);
      }
      $em->persist($assignment);
      $em->flush($assignment);
      $em->clear($assignment);

      $fulfilment = $em->getRepository('BWLwebshopBundle:RequirementFulfilment')->findByIds($assignment, $requirement);
      if(is_null($fulfilment)) {
        $fulfilment = new RequirementFulfilment();
        $fulfilment->setRequirement($requirement);
        $fulfilment->setAssignment($assignment);
        $em->persist($fulfilment);
        $em->flush($fulfilment);
        $em->clear($fulfilment);
      }

      // Set requirement derivations
      $derivation = $em->getRepository('BWLwebshopBundle:RequirementDerivation')->findByIds($assignment, $requirement);
      if(is_null($derivation)) {
        $derivation = new RequirementDerivation();
        $derivation->setParentItem($component);
        $derivation->setParentItemDate($assignmentDate);
        $derivation->setRequirement($requirement);
        $derivation->setAssignment($assignment);
        $em->persist($derivation);
        $em->flush($derivation);
        $em->clear($derivation);
      }

      $this->updateRequirementsAndAssignmentsByComponent($child_component, $amount, $childRequirementDate, $childAssignmentDate);
    }
  }

}